//GNU C библиотека.
#include <linux/videodev.h>


//Библиотека Qt.
#include <QtGui>
#include <QX11Info>


//Внутренняя библиотека.
#include "xvideowidget.h"
#include "vidframe.h"
#include "videvent.h"


using namespace std;
std::set<XvPortID> XVideoWidget::grabbedPorts;


//===========================================================================//
//=Заголовок: XVideoWidget::XVideoWidget() - public
//=Параметры:
//=Описание: Конструктор.
//===========================================================================//
XVideoWidget::XVideoWidget(int index, QWidget *parent) : VidWidget(index, parent)
{
port = 0; xvImage = NULL; vidFrame = NULL; isCapturing = false;

setAttribute(Qt::WA_PaintOnScreen);
setAttribute(Qt::WA_NoSystemBackground);

connect(this, SIGNAL(needRepaint()), this, SLOT(repaint()), Qt::QueuedConnection);
}


//===========================================================================//
//=Заголовок: XVideoWidget::~XVideoWidget() - public
//=Параметры:
//=Описание: Деструктор.
//===========================================================================//
XVideoWidget::~XVideoWidget() {stopVideo();}


//===========================================================================//
//=Заголовок: VidWidget::WINTYPE XVideoWidget::getType() - public
//=Параметры:
//=Описание: Функция возврата типа видеоокна.
//===========================================================================//
VidWidget::WINTYPE XVideoWidget::getType() {return VidWidget::XVIDEO;}


//===========================================================================//
//=Заголовок: void XVideoWidget::paintEvent() - protected
//=Параметры:
//=Описание: Событие перерисовки виджета.
//===========================================================================//
void XVideoWidget::paintEvent(QPaintEvent *event)
{
if(!vidFrame) {event->accept(); return;}

if(!isCapturing)
	{startVideo(); event->accept(); return;}

Display *display = x11Info().display();
if(!display || port == 0) {vidFrame = NULL; return;}

if(xvImage == NULL)
	{
	int pixfmt = v4l2ToXVideoPixFmt(vidFrame->getV4L2PixFormat());
	if(pixfmt == -1) {vidFrame = NULL; return;}
	xvImage = XvCreateImage(display, port, pixfmt, (char *)vidFrame->getDeinterlacedData(), vidFrame->getWidth(), vidFrame->getHeight());
}
else xvImage->data = (char *)vidFrame->getDeinterlacedData();

XGCValues xgcv;
GC gc = XCreateGC(display, winId(), 0L, &xgcv);
XvPutImage(display, port, winId(), gc, xvImage, 0, 0, xvImage->width, xvImage->height, 0, 0, width(), height());
XFreeGC(display, gc);

vidFrame = NULL;

event->accept();
}


//===========================================================================//
//=Заголовок: void XVideoWidget::customEvent() - protected
//=Параметры:
//=Описание: Событие уровня приложения.
//===========================================================================//
void XVideoWidget::customEvent(QEvent *event)
{
VidEvent *videvent = (VidEvent *)event;
if(!videvent) return;

vidFrame = videvent->getFrame();
if(!vidFrame)
	{isCapturing = false; stopVideo(); event->accept(); return;}

emit needRepaint();

event->accept();
}


//===========================================================================//
//=Заголовок: void XVideoWidget::startVideo() - private
//=Параметры:
//=Описание: Функция активации видеопотока.
//===========================================================================//
void XVideoWidget::startVideo()
{
if(isCapturing) return;

Display *display = x11Info().display();
if(!display) return;

unsigned int ver, rel, req, ev, err;
if(XvQueryExtension(display, &ver, &rel, &req, &ev, &err ) != Success) return;

unsigned int numadaptors;
XvAdaptorInfo *adaptorinfo = NULL;
if(XvQueryAdaptors(display, DefaultRootWindow(display), &numadaptors, &adaptorinfo) != Success) return;

for(int i = 0; i < (signed)numadaptors; i++)
	{
	if((adaptorinfo[i].type & (XvInputMask | XvImageMask)) == (XvInputMask | XvImageMask))
		{
		for(int p = adaptorinfo[i].base_id;	p < (signed)(adaptorinfo[i].base_id + adaptorinfo[i].num_ports); p++)
			{
			if(grabbedPorts.find(p) == grabbedPorts.end())
				{
				if(XvGrabPort(display, p, CurrentTime) == Success)
					{grabbedPorts.insert(p); port = p; break;}
			}
		}
		if(port != 0) break;
	}
}

XvFreeAdaptorInfo(adaptorinfo);
if(port == 0) return;

try {
	Atom xvColourKey = findAtom(display, "XV_COLORKEY");
	if(xvColourKey != None)
		{
		requireColourKey = true;
		if(XvGetPortAttribute(display, port, xvColourKey, &colourKey) != Success) return;

		Atom xvAutoPaint = findAtom(display, "XV_AUTOPAINT_COLORKEY");
		if(xvAutoPaint != None) {XvSetPortAttribute(display, port, xvAutoPaint, 0);}
	}
	else {requireColourKey = false;}
}
catch(...) {
	XvUngrabPort(display, port, CurrentTime);
	grabbedPorts.erase(port);
	return;
}

isCapturing = true;
}


//===========================================================================//
//=Заголовок: void XVideoWidget::stopVideo() - private
//=Параметры:
//=Описание: Функция остановки видеопотока.
//===========================================================================//
void XVideoWidget::stopVideo()
{
vidFrame = NULL; isCapturing = false;
if(xvImage != NULL) {XFree(xvImage); xvImage = NULL; update();}
if(port != 0)
	{
	Display *display = x11Info().display();
	if(display != NULL) XvUngrabPort(display, port, CurrentTime);
	grabbedPorts.erase(port); port = 0;
}
}


//===========================================================================//
//=Заголовок: Atom XVideoWidget::findAtom() - private
//=Параметры:
//=Описание: Функция поиска "атома" (хрен знает что).
//===========================================================================//
Atom XVideoWidget::findAtom(Display *display, const char *name)
{
if(port == 0) return None;

XvAttribute *attributes;
int numAttributes;
attributes = XvQueryPortAttributes(display, port, &numAttributes);
if(attributes == NULL) return None;

Atom retVal = None;
for(int i = 0; i < numAttributes; i++)
	if(strcmp(attributes[i].name, name) == 0)
		{retVal = XInternAtom(display, name, False); break;}

XFree(attributes);
return retVal;
}


//===========================================================================//
//=Заголовок: int XVideoWidget::v4l2ToXVideoPixFmt() - private
//=Параметры:
//=Описание: Функция конвертации видеоформата v4l2 в XVideo.
//===========================================================================//
int XVideoWidget::v4l2ToXVideoPixFmt(int v4l2pixfmt)
{
QString pixfmtstr;
if(v4l2pixfmt == V4L2_PIX_FMT_RGB32)        pixfmtstr = "RGBA-32";
else if(v4l2pixfmt == V4L2_PIX_FMT_RGB555X) pixfmtstr = "RGBT-16";
else if(v4l2pixfmt == V4L2_PIX_FMT_RGB555)  pixfmtstr = "RGB-16";
else if(v4l2pixfmt == V4L2_PIX_FMT_RGB24)   pixfmtstr = "RGB-24";
else if(v4l2pixfmt == V4L2_PIX_FMT_YUYV)    pixfmtstr = "YUY2-YUYV";
else if(v4l2pixfmt == V4L2_PIX_FMT_UYVY)    pixfmtstr = "UYVY-UYVY";
else if(v4l2pixfmt == V4L2_PIX_FMT_YVU420)  pixfmtstr = "YV12-YVU";
else if(v4l2pixfmt == V4L2_PIX_FMT_YUV420)  pixfmtstr = "I420-YUV";
else return -1;

Display *display = x11Info().display();
if(!display) return -1;
int count = 0;
XvImageFormatValues *fmts = XvListImageFormats(display, port, &count);
for(int i = 0; i < count; ++i)
	{
	switch(fmts[i].type)
		{
		case XvRGB: {
			char *name = new char[strlen((char*)fmts[i].guid) + 1];
			strcpy(name, (char*)fmts[i].guid);
			QString pixfmtstr2 = QString(name) + QString("-") + QString::number(fmts[i].depth);
			delete name;
			if(pixfmtstr == pixfmtstr2) return fmts[i].id;
		} break;
		case XvYUV: {
			char *name = new char[strlen((char*)fmts[i].guid) + 1];
			strcpy(name, (char*)fmts[i].guid);
			char *ordername = new char[strlen((char*)fmts[i].component_order) + 1];
			strcpy(ordername, (char*)fmts[i].component_order);
			QString pixfmtstr2 = QString(name) + QString("-") + QString(ordername);
			delete name; delete ordername;
			if(pixfmtstr == pixfmtstr2) return fmts[i].id;
		} break;
	}
}

return -1;
}
